﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using System.Xml;
using BakalarskaPraca.CustomComponents;
using BakalarskaPraca.Forms.Controls;
using BakalarskaPraca.Generation.Data;
using BakalarskaPraca.Generation.Helpers;
using BakalarskaPraca.Generation.Tasks.Abstract;
using BakalarskaPraca.Generation.Visualization;
using BakalarskaPraca.Interfaces;
using BakalarskaPraca.Properties;
using ColladaDotNet.Geometry;
using Microsoft.DirectX;

namespace BakalarskaPraca.Forms
{
    public partial class MainForm : Form
    {
        private ProjectData _projectData;
        private GeometryVisualiser.PerspectiveTypes _perspective = GeometryVisualiser.PerspectiveTypes.FlatXY;
        private GeometryVisualiser _visualiser;
        private List<Polygon> _markedPolygons;
        private TreeNode _selectedNode;
        private TargetGroupControl _selectedTargetGroupControl;
        private GenerationInstructionForm _generationInstructionForm;
        private SmartSelection _smartSelectionForm;
        
        public MainForm()
        {
            InitializeComponent();
            this._smartSelectionForm = new SmartSelection();
            this.PerspectiveComboBox.SelectedIndex = 0;
        }

        public void ShowData(ProjectData data)
        {
            this.Invoke((MethodInvoker) delegate
                                            {
                                                this.CopySelectedBtn.Enabled = false; 
                                                this._projectData = data;
                                                this._markedPolygons = new List<Polygon>();
                                                this._visualiser = new GeometryVisualiser(this._projectData.Scene.Geometries, this.VisualisationPictureBox.Size); 
                                                this.GeometriesTreeView.Nodes.Clear();
                                                this.DropPanel.Controls.Clear();
                                                this._projectData.Scene
                                                    .Geometries.ForEach(g =>
                                                        this.GeometriesTreeView.Nodes.Add(new GeometryTreeNode(g))
                                                    );
                                                this.ModelInfoLabel.Text = "Model Info - Geometries: " +
                                                                           data.Scene.Geometries.Count() + " | Polygons: " + data.Polygons.Count();
                                                this.RenderVisualization();  
                                                this._generationInstructionForm = new GenerationInstructionForm(this._projectData);
                                            });
        }

        public void RenderVisualization()
        {
            if (this._projectData != null && this._visualiser != null)
            {
                this._visualiser.UpdateSize(this.VisualisationPictureBox.Size, this._perspective);
                this._visualiser.GetVisualization();
                this.DropPanel.Controls.Cast<TargetGroupControl>().ToList().ForEach(
                    target =>
                    {
                        this._visualiser.HighlightFillColor = target.MarkColor;
                        this._visualiser.HighlightEdgeColor = target.SelectionColor;
                        target.PolygonGroup.Polygons.ForEach(
                            p => this._visualiser.GetHighlightedPolygonVisualization(p));
                    });
                this._visualiser.HighlightFillColor = Color.LawnGreen;
                this._visualiser.HighlightEdgeColor = Color.Green;
                this._markedPolygons.ForEach(
                    poly => this._visualiser.GetHighlightedPolygonVisualization(poly));
                this._visualiser.SetDefaultColors();
                this.DrawSelectedNode();
                this.VisualisationPictureBox.Image = this._visualiser.GetCurrentFrame();
            }
        }

        private void ExitToolStripMenuItemClick(object sender, EventArgs e)
        {
            this.Close();
        }

        public void LoadData(string modelFilePath)
        {
            ProjectData data = new ProjectData();
            data.ModelFileName = modelFilePath;
            data.XmlDocument = new XmlDocument();
            data.XmlDocument.Load(modelFilePath);
            this.projectLoader1.Left = this.Width / 2 - this.projectLoader1.Width / 2;
            this.projectLoader1.Top = this.Height / 2 - this.projectLoader1.Height / 2;
            this.projectLoader1.Show();
            this.projectLoader1.StartLoading(data, ShowData);
        }

        private void MarkPolygon(PolygonTreeNode polygonTreeNode, bool mark = true)
        {
            if (mark)
            {
                this._markedPolygons.Add(polygonTreeNode.Polygon);
            }
            else
            {
                this._markedPolygons.RemoveAll(polygonTreeNode.Polygon.Equals);
            }
            this.CopySelectedBtn.Enabled = this._markedPolygons.Any();
        }

        private void MarkPolygon(GeometryTreeNode geometryTreeNode, bool mark = true)
        {
            geometryTreeNode.Nodes.Cast<PolygonTreeNode>()
                .ToList().ForEach(p => this.MarkPolygon(p, mark));
        }

        private void NewProjectToolStripMenuItemClick(object sender, EventArgs e)
        {
            InputForm form = new InputForm();
            form.ShowDialog(this);
            if (form.DialogResult == DialogResult.OK)
            {
                this.LoadData(Properties.Settings.Default.InputFilePath);
            }
        }

        private void VisualisationPictureBoxSizeChanged(object sender, EventArgs e)
        {
            this.RenderVisualization();
        }

        private void GeometriesTreeViewAfterSelect(object sender, TreeViewEventArgs e)
        {
            this._selectedNode = e.Node; 
            this.RenderVisualization();
        }

        private void DrawSelectedNode()
        {
            if (this._selectedNode == null)
                return;
            if (this._selectedNode is GeometryTreeNode)
            {
                this.VisualisationPictureBox.Image =
                    this._visualiser.GetHighlightedVisualization((this._selectedNode as GeometryTreeNode).Geometry);
            }
            else if (this._selectedNode is PolygonTreeNode)
            {
                this.VisualisationPictureBox.Image =
                    this._visualiser.GetHighlightedPolygonVisualization((this._selectedNode as PolygonTreeNode).Polygon);
            }
        }
        
        private void PerspectiveComboBoxSelectedIndexChanged(object sender, EventArgs e)
        {
            this._perspective = (GeometryVisualiser.PerspectiveTypes) this.PerspectiveComboBox.SelectedIndex;
            this.RenderVisualization();
        }

        private void GeometriesTreeViewAfterCheck(object sender, TreeViewEventArgs e)
        {
            if (e.Node is GeometryTreeNode)
            {
                e.Node.Nodes.Cast<PolygonTreeNode>().ToList().ForEach(
                    p => p.Checked = e.Node.Checked);
            }
            else
            {
                this.MarkPolygon(e.Node as PolygonTreeNode, e.Node.Checked);
            }
            if (e.Action != TreeViewAction.Unknown)
            {
                this.RenderVisualization();
            }
        }

        private void GeometriesTreeViewLeave(object sender, EventArgs e)
        {
            this.GeometriesTreeView.SelectedNode = null;
            this._selectedNode = null;
            this.RenderVisualization();
        }

        private void GeometriesTreeViewEnter(object sender, EventArgs e)
        {
            
        }

        private void CopySelectedBtnClick(object sender, EventArgs e)
        {
            bool showError = false;
            List<HumidityPolygon> humidityPolygons = new List<HumidityPolygon>();
            this._markedPolygons.ForEach(p =>
                                             {
                                                 if (p.Normal.Z > 0 || p.Normal.Z < 0)
                                                     humidityPolygons.Add(new HumidityPolygon(p));
                                                 else
                                                     showError = true;
                                             });
            if (showError)
            {
                MessageBox.Show(Messages.VerticalPolygonSelectedError);
            }
            if (!humidityPolygons.Any())
            {
                MessageBox.Show(Messages.NoSelectedPolygons);
                return;
            }    
            List<GenerationProcess> instructions = this._generationInstructionForm.GetGenerationInstructions(humidityPolygons);
            if (instructions == null || !instructions.Any())
            {
                return;
            }
            TargetGroupControl t = new TargetGroupControl(humidityPolygons.Cast<Polygon>().ToList(), this.SelectTargetGroup, this.RenderVisualization);
            t.PolygonGroup.GenerationInstructions = instructions;
            int width = this.DropPanel.Width;
            if (this.DropPanel.VerticalScroll.Visible)
            {
                width -= 25;
            } 
            t.Width = width - 2;
            this.DropPanel.Controls.Add(t);
            this.RenderVisualization();
        }

        private void DropPanelSizeChanged(object sender, EventArgs e)
        {
            foreach (Control ctrl in this.DropPanel.Controls)
            {
                int width = this.DropPanel.Width;
                if (this.DropPanel.VerticalScroll.Visible)
                {
                    width -= 25;
                }
                ctrl.Width = width - 2;
            }
        }

        private void ClearSelectedBtnClick(object sender, EventArgs e)
        {
            this.ClearSelection();
        }

        private void ClearSelection()
        {
            this.CopySelectedBtn.Enabled = false;
            foreach (TreeNode treeNode in this.GeometriesTreeView.Nodes)
            {
                treeNode.Checked = false;
            }
            this.RenderVisualization();
        }

        private void SelectTargetGroup(TargetGroupControl targetGroupControl)
        {
            this.DropPanel.Focus();
            if (this._selectedTargetGroupControl != null)
            {
                this._selectedTargetGroupControl.SelectControl(false);
            }
            this.ClearSelection();
            this._selectedTargetGroupControl = targetGroupControl;
            this._selectedTargetGroupControl.SelectControl();
            this.MarkPolygons(this._selectedTargetGroupControl.PolygonGroup.Polygons.Cast<Polygon>().ToList(), true);
            this.RenderVisualization();
        }

        private void MarkPolygon(Polygon polygon, bool mark, bool revert = false)
        {
            this.GeometriesTreeView.Nodes.Cast<GeometryTreeNode>().ToList().ForEach(
                g =>
                    {
                        List<PolygonTreeNode> nodes = g.Nodes.Cast<PolygonTreeNode>().ToList();
                        nodes.ForEach(
                            p =>
                                {
                                    if (p.Polygon.Equals(polygon))
                                    {
                                        p.Checked = revert ? !p.Checked : mark;
                                    }
                                });
                        if (nodes.All(n => n.Checked))
                        {
                            g.Checked = true;
                        }
                        else if (nodes.All(n => !n.Checked))
                        {
                            g.Checked = false;
                        }
                    });
        }

        private void MarkPolygons(List<Polygon> polygons, bool mark, bool revert = false)
        {
            this.GeometriesTreeView.Nodes.Cast<GeometryTreeNode>().ToList().ForEach(
                g =>
                {
                    List<PolygonTreeNode> nodes = g.Nodes.Cast<PolygonTreeNode>().ToList();
                    nodes.ForEach(
                        p =>
                        {
                            if (polygons.Any(p.Polygon.Equals))
                            {
                                p.Checked = revert ? !p.Checked : mark;
                            }
                        });
                    if (nodes.All(n => n.Checked))
                    {
                        g.Checked = true;
                    }
                    else if (nodes.All(n => !n.Checked))
                    {
                        g.Checked = false;
                    }
                });
        }

        private void DropPanelClick(object sender, EventArgs e)
        {
            this.DropPanel.Focus(); 
            if (this._selectedTargetGroupControl == null) return;
            this._selectedTargetGroupControl.SelectControl(false);
            this._selectedTargetGroupControl = null;
        }

        private void DropPanelLeave(object sender, EventArgs e)
        {
            if (this._selectedTargetGroupControl == null) return;
            this._selectedTargetGroupControl.SelectControl(false);
            this._selectedTargetGroupControl = null;
        }

        private void VisualisationPictureBoxClick(object sender, EventArgs e)
        {
            this.VisualisationPictureBox.Focus();
        }

        private void SmartGroundSelectionToolStripMenuItemClick(object sender, EventArgs e)
        {
            if (this._projectData == null) return;
            int c = this._projectData.Polygons.Count;
            int i = 0;
            List<Polygon> polygons = new List<Polygon>();
            this._projectData.Polygons.ForEach(
                                    p =>
                                        {
                                            if (p.Normal.X.Equals(0) && p.Normal.Y.Equals(0))
                                            {
                                                polygons.Add(p);
                                                i++;
                                            }
                                        }
                            );
            this.MarkPolygons(polygons, true);
            this.RenderVisualization();
        }

        private void SelectAllToolStripMenuItemClick(object sender, EventArgs e)
        {
            this.MarkPolygons(this._projectData.Polygons.Cast<Polygon>().ToList(), true);
            this.RenderVisualization();
        }

        private void SelectInverseToolStripMenuItemClick(object sender, EventArgs e)
        {
            this.MarkPolygons(this._projectData.Polygons.Cast<Polygon>().ToList(), true, true);
            this.RenderVisualization();
        }

        private void StartGenerationToolStripMenuItemClick(object sender, EventArgs e)
        {
            if (this.DropPanel.Controls.Count > 0)
            {
                if (this.MainFolderBrowser.ShowDialog() != DialogResult.OK)
                {
                    MessageBox.Show("You haven't specified the output of your file.");
                    return;
                }
                ProgressViewer viewer = new ProgressViewer(this.DropPanel.Controls.Cast<TargetGroupControl>().Select(ctrl => ctrl.PolygonGroup).ToList(), this.GenerationComplete);
                viewer.ShowDialog();
            }
            else
            {
                MessageBox.Show("You didn't add any generation instructions");
            }
        }

        private void GenerationComplete()
        {
            string targetPath = this.MainFolderBrowser.SelectedPath;
            PlantModel.GetPlantRequirements().ForEach(
                    path =>
                        {
                            if (!path.Equals(""))
                            {
                                if (Directory.Exists(path))
                                {
                                    DirectoryInfo di = new DirectoryInfo(path);
                                    string newDirPath = Path.Combine(targetPath, di.Name);
                                    if (!Directory.Exists(newDirPath))
                                    {
                                        Directory.CreateDirectory(newDirPath);
                                    }
                                    foreach (FileInfo fi in di.GetFiles())
                                    {
                                        fi.CopyTo(Path.Combine(newDirPath, fi.Name), true);
                                    }
                                } 
                                else
                                {
                                    MessageBox.Show("The path '" + path + "' does not exist");
                                }
                            }
                        }
                );
            this._projectData.XmlDocument.Save(targetPath + "/output.dae");
        }

        
        private List<Polygon> SelectAdjacentPolygons(Polygon p, float maxAngle)
        {
            HashSet<Polygon> markedPolygons = new HashSet<Polygon>();
            Queue<Polygon> suitablePolygonsQueue = new Queue<Polygon>();
            suitablePolygonsQueue.Enqueue(p);
            while(suitablePolygonsQueue.Any())
            {
                Polygon first = suitablePolygonsQueue.Dequeue();
                this._projectData.Polygons.Where(
                    polygon =>
                        first != polygon &&
                        this.PolygonsAdjacent(first, polygon) && 
                        !markedPolygons.Contains(polygon) &&
                        this.GetNormalsAngle(first, polygon) <= maxAngle
                        )
                            .ToList()
                            .ForEach(suitablePolygonsQueue.Enqueue);
                    markedPolygons.Add(first);
            }
            Console.WriteLine(markedPolygons.Count);
            return markedPolygons.ToList();
        }

        private float GetNormalsAngle(Polygon first, Polygon second)
        {
            return (float)(Math.Acos((double)Vector3.Dot(first.Normal, second.Normal) / (first.Normal.Length() * second.Normal.Length())) / Math.PI * 180);
        }

        private bool PolygonsAdjacent(Polygon first, Polygon second)
        {
            return first.Vertices.Count(
                vertex => second.Vertices.Any(vertex2 => vertex.Positions[0].Distance(vertex2.Positions[0]) - 0 < 1E-3)) >= 2;
        }

        private int i;
        private void VisualisationPictureBoxMouseDown(object sender, MouseEventArgs e)
        {
            if (this._projectData == null)
                return;
            switch (e.Button)
            {
                case MouseButtons.Left:
                    {
                        bool shiftPressed = (ModifierKeys & Keys.Shift) == Keys.Shift;
                        bool ctrlPressed = (ModifierKeys & Keys.Control) == Keys.Control;

                        if (!shiftPressed && !ctrlPressed)
                            this.ClearSelection();

                        var selected = this._projectData.Polygons.Where(
                            polygon =>
                            this._visualiser.IsPointInsidePolygon(e.X, e.Y, polygon)
                            ).ToList();

                        if (selected.Any())
                        {
                            this.MarkPolygons(selected, true, ctrlPressed);
                        }

                
                        this.RenderVisualization();
                        Console.WriteLine(i++);
                    }
                    break;
                case MouseButtons.Right:
                    {
                        bool shiftPressed = (ModifierKeys & Keys.Shift) == Keys.Shift;
                        bool ctrlPressed = (ModifierKeys & Keys.Control) == Keys.Control;
                        if (this._smartSelectionForm.ShowDialog() == DialogResult.OK)
                        {
                            float maxAngle = this._smartSelectionForm.AngleTB.FloatValue + 1;
                            var l = this._projectData.Polygons.Where(
                              polygon =>
                              this._visualiser.IsPointInsidePolygon(e.X, e.Y, polygon)
                              ).ToList();
                            if (l.Any())
                            {
                                if (!shiftPressed && !ctrlPressed)
                                    this.ClearSelection();
                                
                                this.MarkPolygons(this.SelectAdjacentPolygons(l.First(), maxAngle), true);
                                this.RenderVisualization();
                            }  
                        }
                    }
                    break;
            }
        }

        private void CloseAfterGenerationToolStripMenuItemCheckedChanged(object sender, EventArgs e)
        {
            Settings.Default.CloseAfterGeneration = this.closeAfterGenerationToolStripMenuItem.Checked;
        }

        private void HelpToolStripMenuItemClick(object sender, EventArgs e)
        {
            Process.Start(Properties.Settings.Default.HelpURL);
        }
    }
}
